package cn.skyquant.quant4j.jforex.strategy.titans;

import cn.skyquant.quant4j.jforex.sdk.enums.LabelType;
import cn.skyquant.quant4j.jforex.sdk.enums.OpenType;
import cn.skyquant.quant4j.jforex.sdk.strategy.*;
import cn.skyquant.quant4j.jforex.sdk.strategy.BaseConfig;
import cn.skyquant.quant4j.sdk.enums.TradeDirection;
import cn.skyquant.quant4j.sdk.util.time.CalendarUtils;
import com.dukascopy.api.*;
import com.dukascopy.api.feed.IBarFeedListener;
import com.dukascopy.api.feed.IFeedDescriptor;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashMap;
import java.util.Map;

@Library("quant4j-api.jar;quant4j-sdk.jar;quant4j-jforex-sdk.jar;freemarker-2.3.28.jar;httpclient-4.5.6.jar;httpcore-4.4.10.jar;fastjson-1.2.49.jar;commons-codec-1.11.jar")
public class Titans004 extends BaseStrategy implements IBarFeedListener {

    @Configurable("货币")
    public Instrument i1 = Instrument.EURUSD;
    @Configurable("大K线")
    public Period bigPeriod = Period.DAILY;
    @Configurable("布林轨:计算K柱个数(也用作ATR个数)")
    public int boll_nums = 20;
    @Configurable("布林轨:价格选择")
    public IIndicators.AppliedPrice appliedPrice = IIndicators.AppliedPrice.CLOSE;
    @Configurable("布林轨:均线类型")
    public IIndicators.MaType boll_ma_type = IIndicators.MaType.WMA;
    @Configurable("订单成交日志")
    public boolean log_fill = true;
    @Configurable("订单提交日志")
    public boolean log_submit = true;
    @Configurable("订单移仓日志")
    public boolean log_move = true;

    final Map<Instrument, InstrumentEntity> insMap = new HashMap<>();//交易品种实体

    final Map<Instrument, Boll> boll_1_ask_map = new HashMap<>();
    final Map<Instrument, Boll> boll_1_bid_map = new HashMap<>();
    final Map<Instrument, Boll> boll_2_ask_map = new HashMap<>();
    final Map<Instrument, Boll> boll_2_bid_map = new HashMap<>();
    final Map<Instrument, Double> atr_map = new HashMap<>();
    final Map<Instrument, Boolean> can_run_map = new HashMap<>();

    @Override
    protected String getVersion() {
        return "004";
    }

    @Override
    protected String getName() {
        return "Titans" + eaId;
    }

    @Override
    protected String getEaName() {
        return "Titans";
    }

    @Override
    protected void onTickEx(Instrument instrument, ITick tick) {
    }

    @Override
    protected void onBarEx(Instrument instrument, Period period, IBar askBar, IBar bidBar) {
    }

    @Override
    protected void onAccountEx(IAccount account) {
    }

    @Override
    public void onStopEx() {
        super.onStopEx();
    }

    @Override
    protected void init(InitEntity initEntity) {
        TitansConfig titansConfig = new TitansConfig(getName(), 0, 1, 0.2, i1);
        initEntity.a.add(titansConfig);
        context.subscribeToBarsFeed(i1, bigPeriod, OfferSide.BID, this);
        initEntity.r = true;
        insMap.put(i1, new InstrumentEntity(i1));
        IChart chart = context.getChart(i1);
        if (chart != null) {
            IFeedDescriptor feedDescriptor = chart.getFeedDescriptor();
            if (feedDescriptor != null) {
                feedDescriptor.setPeriod(bigPeriod);
                feedDescriptor.setOfferSide(OfferSide.ASK);
                chart.setFeedDescriptor(feedDescriptor);
                chart.add(indicators.getIndicator("BBANDS"), new Object[]{boll_nums, 1d, 1d, boll_ma_type.ordinal()});
                chart.add(indicators.getIndicator("BBANDS"), new Object[]{boll_nums, 2d, 2d, boll_ma_type.ordinal()});
                chart.repaint();
            }
        }
    }

    @Override
    public void onBar(Instrument instrument, Period period, OfferSide offerSide, IBar bar) {
        if (period.equals(bigPeriod)) {
            boolean f = calc(instrument, bar);
            can_run_map.put(instrument, f);
        }

        try {
            process(instrument, bar, period);
        } catch (JFException e) {
            err("process error:%s", e.getMessage());
        }
    }

    private boolean calc(Instrument instrument, IBar bar) {
        try {
            long barstart = history.getBarStart(bigPeriod, context.getTime());
            double[] atrs = indicators.atr(i1, bigPeriod, OfferSide.BID, boll_nums, Filter.WEEKENDS, 1, barstart, 0);
            if (atrs == null || atrs.length == 0) {
                return false;
            }
            double atr = atrs[0];
            atr_map.put(instrument, atr);

            double[][] bolls_1_ask = indicators.bbands(i1, bigPeriod, OfferSide.ASK, appliedPrice, boll_nums, 1, 1, boll_ma_type, Filter.WEEKENDS, 1, bar.getTime(), 0);
            double[][] bolls_2_ask = indicators.bbands(i1, bigPeriod, OfferSide.ASK, appliedPrice, boll_nums, 2, 2, boll_ma_type, Filter.WEEKENDS, 1, bar.getTime(), 0);
            double[][] bolls_1_bid = indicators.bbands(i1, bigPeriod, OfferSide.BID, appliedPrice, boll_nums, 1, 1, boll_ma_type, Filter.WEEKENDS, 1, bar.getTime(), 0);
            double[][] bolls_2_bid = indicators.bbands(i1, bigPeriod, OfferSide.BID, appliedPrice, boll_nums, 2, 2, boll_ma_type, Filter.WEEKENDS, 1, bar.getTime(), 0);
            if (bolls_1_ask == null || bolls_1_ask.length != 3 || bolls_1_bid == null || bolls_1_bid.length != 3) {
                return false;
            }
            if (bolls_2_ask == null || bolls_2_ask.length != 3 || bolls_2_bid == null || bolls_2_bid.length != 3) {
                return false;
            }
            Boll boll_1_ask = new Boll(bolls_1_ask[0][0], bolls_1_ask[1][0], bolls_1_ask[2][0]);
            Boll boll_1_bid = new Boll(bolls_1_bid[0][0], bolls_1_bid[1][0], bolls_1_bid[2][0]);
            Boll boll_2_ask = new Boll(bolls_2_ask[0][0], bolls_2_ask[1][0], bolls_2_ask[2][0]);
            Boll boll_2_bid = new Boll(bolls_2_bid[0][0], bolls_2_bid[1][0], bolls_2_bid[2][0]);
            boll_1_ask_map.put(instrument, boll_1_ask);
            boll_1_bid_map.put(instrument, boll_1_bid);
            boll_2_ask_map.put(instrument, boll_2_ask);
            boll_2_bid_map.put(instrument, boll_2_bid);
        } catch (JFException e) {
            err("calc error:%s", e.getMessage());
        }
        return true;
    }

    private void process(Instrument instrument, IBar bar, Period period) throws JFException {
        boolean canrun = can_run_map.get(instrument);
        if (!canrun) {
            return;
        }
        ITimeDomain timeDomain0;
        ITimeDomain timeDomain1;
        try {
            timeDomain0 = dataService.getOfflineTimeDomain(0);
            timeDomain1 = dataService.getOfflineTimeDomain(-1);
        } catch (JFException e) {
            if (log_error) warn("onBar.getOfflineTimeDomain error:%s", e);
            return;
        }
        if (timeDomain0 == null || timeDomain1 == null) {
            if (log_error) warn("onBar.getOffline,TimeDomain is null");
            return;
        }
        //停盘时间不干活
        if (timeDomain1.getEnd() > context.getTime() || context.getTime() > timeDomain0.getStart()) {
            return;
        }

        for (BaseConfig config : configList) {
            if (!(config instanceof TitansConfig)) {
                continue;
            }
            TitansConfig titansConfig = (TitansConfig) config;
            if (titansConfig.instrument != instrument) {
                continue;
            }
            if (period.equals(bigPeriod)) {
                processByConfig(titansConfig);
            }
        }
    }

    volatile int seriesIndex = 0;

    //开仓
    private void processByConfig(TitansConfig x) throws JFException {
        InstrumentEntity ie = insMap.get(x.instrument);
        long barnextstart = history.getNextBarStart(bigPeriod, context.getTime());
        double equity = account.getEquity();
        //计算昨天的布林轨

        Boll boll_1_ask = boll_1_ask_map.get(x.instrument);
        Boll boll_1_bid = boll_1_bid_map.get(x.instrument);
        Boll boll_2_ask = boll_2_ask_map.get(x.instrument);
        Boll boll_2_bid = boll_2_bid_map.get(x.instrument);
        //首先判断价格在哪里
        ITick tick = history.getLastTick(x.instrument);
        if (tick.getBid() > boll_2_ask.up) {
            //在二轨上轨上方
            submitOrder(x, boll_2_ask.up, tick.getBid(), TradeDirection.SHORT);
        } else if (tick.getAsk() < boll_2_bid.up && tick.getBid() > boll_1_ask.up) {
            //在一轨上轨上方和二轨上轨下方
            submitOrder(x, boll_2_ask.up, tick.getAsk(), TradeDirection.LONG);
            submitOrder(x, boll_1_ask.up, tick.getBid(), TradeDirection.SHORT);
        } else if (tick.getAsk() < boll_1_bid.up && tick.getBid() > boll_1_ask.m) {
            //在一轨中轨上方和一轨中轨下方
            submitOrder(x, boll_1_bid.up, tick.getAsk(), TradeDirection.LONG);
            submitOrder(x, boll_1_ask.m, tick.getBid(), TradeDirection.SHORT);
        } else if (tick.getAsk() < boll_1_bid.m && tick.getBid() > boll_1_ask.low) {
            //在一轨中轨下方和一轨下轨上方
            submitOrder(x, boll_1_bid.m, tick.getAsk(), TradeDirection.LONG);
            submitOrder(x, boll_1_ask.low, tick.getBid(), TradeDirection.SHORT);
        } else if (tick.getAsk() < boll_1_bid.low && tick.getBid() > boll_2_ask.low) {
            //在一轨下轨下方和二轨下轨上方
            submitOrder(x, boll_1_bid.low, tick.getAsk(), TradeDirection.LONG);
            submitOrder(x, boll_2_ask.low, tick.getBid(), TradeDirection.SHORT);
        } else if (tick.getAsk() < boll_2_bid.low) {
            submitOrder(x, boll_2_bid.low, tick.getAsk(), TradeDirection.LONG);
        }
    }

    private void submitOrder(TitansConfig x, double price, double sl, TradeDirection td) throws JFException {
        long barnextstart = history.getNextBarStart(bigPeriod, context.getTime());
        long gtc = barnextstart - Period.FIVE_MINS.getInterval();
        Instrument instrument = x.instrument;
        int first_equit = (int) account.getEquity();
        InstrumentEntity ie = insMap.get(instrument);
        price = new BigDecimal(price).setScale(ie.scale + 1, RoundingMode.HALF_UP).doubleValue();
        sl = new BigDecimal(sl).setScale(ie.scale + 1, RoundingMode.HALF_UP).doubleValue();
        double tp = new BigDecimal(td.isLong() ? (price + Math.abs(price - sl)) : (price - Math.abs(price - sl))).setScale(ie.scale + 1, RoundingMode.HALF_UP).doubleValue();
        int si = seriesIndex++;
        OrderEx sell_orderEx = new OrderEx(x.name, LabelType.M, x.index, si, x.instrument, td);
        IEngine.OrderCommand orderCommand = td.isLong() ? IEngine.OrderCommand.BUYSTOP : IEngine.OrderCommand.SELLSTOP;
        String label = sell_orderEx.toString();
        TitansOrderComment sell_orderComment = new TitansOrderComment(0, si, OpenType.P, first_equit, context.getTime(), 0);
        String comment = sell_orderComment.toString();
        engine.submitOrder(label, x.instrument, orderCommand, 0.001, price, 5, sl, tp, gtc, comment);
    }


    @Override
    protected void onMessageEx(IMessage message) {
        if (message.getOrder() == null) {
            return;
        }
        IOrder order = message.getOrder();
        OrderEx orderEx = new OrderEx(order, new TitansOrderComment());
        if (!orderEx.valid(getName())) {
            return;
        }
        Instrument instrument = order.getInstrument();
        InstrumentEntity ie = insMap.get(order.getInstrument());
        double amount = order.getAmount();
        double sl = order.getStopLossPrice();
        double tp = order.getTakeProfitPrice();
        double openprice = order.getOpenPrice();
        IEngine.OrderCommand orderCommand = order.getOrderCommand();
        switch (message.getType()) {
            case ORDER_FILL_OK:
                if (log_fill)
                    notif("Titans Fill, %s %s[%s], open_price:%s,amount=%s,tp=%s,sl=%s", instrument.name(), orderCommand.name(), order.getLabel(), openprice, amount, tp, sl);
                break;
            case ORDER_SUBMIT_OK:
                if (log_submit) {
                    out("Titans Submit, label=%s,open_price=%s,amount=%s,tp=%s,sl=%s,create_time=%s,gtc=%s",
                            order.getLabel(),
                            order.getOpenPrice(),
                            order.getAmount(),
                            order.getTakeProfitPrice(),
                            order.getStopLossPrice(),
                            CalendarUtils.formatGMTStandard(order.getCreationTime()),
                            CalendarUtils.formatGMTStandard(order.getGoodTillTime()));

                }
                break;
            case ORDER_CHANGED_OK:
                if (log_move && message.getReasons().contains(IMessage.Reason.ORDER_CHANGED_SL)) {
                    out("Titans Move SL, label=%s,amount=%s,open_price=%s,sl=%s",
                            order.getLabel(),
                            order.getAmount(),
                            order.getOpenPrice(),
                            order.getStopLossPrice());
                }
                break;
            case ORDER_CLOSE_OK:
                double closePrice = order.getClosePrice();
                double profit = order.getProfitLossInUSD();
                if (profit > 0) {
                    info("%s,Titans %s %s Close,opentime=%s,fill_time=%s, open_price:%s,amount=%s,tp=%s,sl=%s,closePrice=%s,profit=%s,profit.pip=%s",
                            account.getBalance(), instrument.name(), orderCommand.name(),
                            CalendarUtils.formatGMTStandard(order.getCreationTime()), CalendarUtils.formatGMTStandard(order.getFillTime()),
                            openprice, amount, tp, sl, closePrice, profit, order.getProfitLossInPips());
                } else if (profit < 0) {
                    err("%s,Titans %s %s Close, opentime=%s,fill_time=%s, open_price:%s,amount=%s,tp=%s,sl=%s,closePrice=%s,profit=%s,profit.pip=%s",
                            account.getBalance(), instrument.name(), orderCommand.name(),
                            CalendarUtils.formatGMTStandard(order.getCreationTime()), CalendarUtils.formatGMTStandard(order.getFillTime()),
                            openprice, amount, tp, sl, closePrice, profit, order.getProfitLossInPips());
                }
                if (profit != 0) {
                    total_sl += profit;
                    if (total_sl > 0) {
                        total_sl = 0;
                    }
                }
                break;
        }
    }

    double total_sl = 0;

    final static class TitansConfig extends BaseConfig {
        final Instrument instrument;//货币

        TitansConfig(String name, int index, double m, double lossLimit, Instrument instrument) {
            super(name, index, m, lossLimit);
            this.instrument = instrument;
        }

        @Override
        public String toString() {
            return "Titans_" + index
                    + "_" + instrument.name()
                    + "_m_" + m;
        }

        @Override
        public boolean isValid() {
            return instrument != null && m > 0;
        }
    }

    final static class TitansOrderComment extends BaseOrderComment {
        public TitansOrderComment() {
        }

        TitansOrderComment(int series_id, int series_index, OpenType type, int first_equit, long first_time, int this_profitloss) {
            super(series_id, series_index, type, first_equit, first_time, this_profitloss);
        }

    }
}
